﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using AMS.SubCipherSolver.Args;

namespace AMS.SubCipherSolver.Models
{
    public class SolverModel
    {
        private string _cipherString;
        private string _solutionString;
        private Dictionary<char, char> _cipherCharsToSolnChars;

        public event EventHandler<SolutionEventArgs> SolutionUpdated;
        public event EventHandler<CipherEventArgs> CipherUpdated;
        public event EventHandler SolutionSaved;
        public event EventHandler KeySaved;
        public event EventHandler<KeyLoadedEventArgs> KeyLoaded;
        public event EventHandler<CipherEventArgs> CipherAnalyzedForDoubleChars;
        public event EventHandler<CipherEventArgs> CipherAnalyzedForCharsInWordsCount;
        public event EventHandler<CipherPatternEventArgs> CipherAnalyzedForCommonWordPatterns;
        public event EventHandler<CipherWordsEventArgs> CipherAnalyzedForShortWords;

        public SolverModel()
        {
            this._cipherCharsToSolnChars = new Dictionary<char, char>();
        }

        public void LoadCipher(FileInfo cipherFile, bool resetKey=true)
        {
            SetCipher(cipherFile);
            if (resetKey)
            {
                _cipherCharsToSolnChars.Clear();
                ResetSolution();
            }
            else
            {
                UpdateSolution();
            }
        }

        private void SetCipher(FileInfo cipherFile)
        {
            using (StreamReader reader = new StreamReader(cipherFile.FullName))
            {
                _cipherString = reader.ReadToEnd();
                reader.Close();
            }
            CipherUpdated.Raise(this, new CipherEventArgs(_cipherString, GetCipherCharCounts()));
            AnalyzeCipherForPatterns();
        }

        private Dictionary<char, int> GetCipherCharCounts()
        {
            Dictionary<char, int> results = new Dictionary<char, int>();

            if (String.IsNullOrEmpty(_cipherString))
                return results;

            for (int i = 0; i < _cipherString.Length; i++)
            {
                char c = _cipherString[i];
                if (char.IsWhiteSpace(c) || c == '\n' || c == '\r')
                    continue;

                if (!results.ContainsKey(c))
                    results.Add(c, 0);

                results[c] += 1;
            }

            return results;
        }

        private void AnalyzeCipherForPatterns()
        {
            if (String.IsNullOrEmpty(_cipherString))
                return;

            AnalyzeCipherForDoubleChars();
            AnalyzeCipherForCharsInWordsCount();
            AnalyzeCipherForCommonWordPatterns();
            AnalyzeCipherForShortWords();
            // TODO: other known patterns; T__T
        }

        private void AnalyzeCipherForDoubleChars()
        {
            Dictionary<char, int> doubleCharCount = new Dictionary<char, int>();
            for (int i = 0; i <= _cipherString.Length - 2; i++)
            {
                if (_cipherString[i] == _cipherString[i + 1])
                {
                    if (!doubleCharCount.ContainsKey(_cipherString[i]))
                        doubleCharCount.Add(_cipherString[i], 0);
                    doubleCharCount[_cipherString[i]] += 1;
                }
            }
            CipherAnalyzedForDoubleChars.Raise(this, new CipherEventArgs(null, doubleCharCount));
        }

        private void AnalyzeCipherForCharsInWordsCount()
        {
            Dictionary<char, int> charInWordsCount = new Dictionary<char, int>();
            string modString = _cipherString.Replace("\n\r", String.Empty);
            string[] words = modString.Split(' ');
            foreach (string word in words)
            {
                HashSet<char> used = new HashSet<char>();
                foreach (char c in word)
                {
                    if (char.IsWhiteSpace(c))
                        continue;
                    if (!used.Contains(c))
                    {
                        if (!charInWordsCount.ContainsKey(c))
                            charInWordsCount.Add(c, 0);
                        charInWordsCount[c] += 1;
                        used.Add(c);
                    }
                }
            }
            CipherAnalyzedForCharsInWordsCount.Raise(this, new CipherEventArgs(null, charInWordsCount));
        }

        private void AnalyzeCipherForCommonWordPatterns()
        {
            Dictionary<string, HashSet<char>> patternsToChars = new Dictionary<string, HashSet<char>>();
            string modString = _cipherString.ReplaceAllWhiteSpace();
            string[] words = modString.Split(' ');

            patternsToChars.Add("ThaT", new HashSet<char>());
            foreach (string word in words)
            {
                if (word.Length == 4 && word[0] == word[3] && !patternsToChars["ThaT"].Contains(word[0]))
                {
                    patternsToChars["ThaT"].Add(word[0]);
                }
            }
            CipherAnalyzedForCommonWordPatterns.Raise(this, new CipherPatternEventArgs(patternsToChars));
        }

        private void AnalyzeCipherForShortWords()
        {
            Dictionary<int, HashSet<string>> wordsDict = new Dictionary<int, HashSet<string>>();
            string modString = _cipherString.ReplaceAllWhiteSpace();
            string[] words = modString.Split(' ');
            foreach (string word in words)
            {
                if (!wordsDict.ContainsKey(word.Length))
                    wordsDict.Add(word.Length, new HashSet<string>());
                if (!wordsDict[word.Length].Contains(word))
                    wordsDict[word.Length].Add(word);
            }
            CipherAnalyzedForShortWords.Raise(this, new CipherWordsEventArgs(wordsDict));
        }

        public void LoadKey(FileInfo keyFile)
        {
            _cipherCharsToSolnChars.Clear();
            bool useLetters, useDigits, usePunct;
            // for each line, parse KeyValuePair
            using (StreamReader reader = new StreamReader(keyFile.FullName))
            {
                //string tt = reader.ReadLine();
                useLetters = reader.ReadLine().Contains("True");
                useDigits = reader.ReadLine().Contains("True");
                usePunct = reader.ReadLine().Contains("True");
                string line = reader.ReadLine();
                while (!String.IsNullOrEmpty(line) && !String.IsNullOrWhiteSpace(line))
                {
                    char cipherChar = line[0];
                    char solnChar = line[2];
                    _cipherCharsToSolnChars.Add(cipherChar, solnChar);
                    line = reader.ReadLine();
                }
            }
            UpdateSolution();
            KeyLoaded.Raise(this, new KeyLoadedEventArgs(useLetters, useDigits, usePunct));
        }

        public void SaveKey(FileInfo keyFile, bool lettersValid, bool digitsValid, bool punctValid, bool overwrite=false)
        {
            // does file exist?  If yes, ask to overwrite
            if (keyFile.Exists)
            {
                if (overwrite)
                    keyFile.Delete();
                else
                    throw new FileLoadException("file exists");
            }

            // for each KeyValuePair in Dictionary; write to string
            StringBuilder sb = new StringBuilder();
            sb.Append(String.Format("{0}\r\n", lettersValid));
            sb.Append(String.Format("{0}\r\n", digitsValid));
            sb.Append(String.Format("{0}\r\n", punctValid));
            foreach (char cipherChar in _cipherCharsToSolnChars.Keys)
            {
                sb.Append(String.Format("{0}|{1}\r\n", cipherChar, _cipherCharsToSolnChars[cipherChar]));
            }

            // save file
            using (StreamWriter writer = new StreamWriter(keyFile.FullName))
            {
                writer.Write(sb.ToString());
                writer.Close();
            }
            KeySaved.Raise(this, EventArgs.Empty);
        }

        public void SaveSolution(FileInfo solnFile, bool overwrite = false)
        {
            if (solnFile.Exists)
            {
                if (overwrite)
                    solnFile.Delete();
                else
                    throw new FileLoadException("file exists");
            }

            using (StreamWriter writer = new StreamWriter(solnFile.FullName))
            {
                writer.Write(_solutionString);
                writer.Close();
            }
            SolutionSaved.Raise(this, EventArgs.Empty);
        }
        
        public void SetCharacter(char cipherChar, char solnChar)
        {
            // one cipher character can only be represented by one solution character
            // one solution character can be set to many cipher characters

            if (solnChar != '_')
            {
                if (_cipherCharsToSolnChars.ContainsKey(cipherChar))
                {
                    _cipherCharsToSolnChars[cipherChar] = solnChar;
                    UpdateSolution(cipherChar, solnChar);
                }
                else
                {
                    _cipherCharsToSolnChars.Add(cipherChar, solnChar);
                    UpdateSolution(cipherChar, solnChar);
                }
            }
            else if (_cipherCharsToSolnChars.ContainsKey(cipherChar))
            {
                _cipherCharsToSolnChars.Remove(cipherChar);
                UpdateSolution(cipherChar, '_');
            }
        }

        private void UpdateSolution(char cipherChar, char solnChar)
        {
            char[] solution = _solutionString.ToCharArray();

            for (int i = 0; i < _cipherString.Length; i++)
            {
                if (_cipherString[i] == cipherChar)
                    solution[i] = solnChar;
            }

            _solutionString = new string(solution);
            SolutionUpdated.Raise(this, new SolutionEventArgs(_solutionString));
        }

        private void UpdateSolution()
        {
            ResetSolution();
            char[] solution = _solutionString.ToCharArray();

            for (int i = 0; i < solution.Length; i++)
            {
                if (_cipherCharsToSolnChars.ContainsKey(_cipherString[i]))
                    solution[i] = _cipherCharsToSolnChars[_cipherString[i]];
            }

            _solutionString = new string(solution);
            SolutionUpdated.Raise(this, new SolutionEventArgs(_solutionString));
        }

        private void ResetSolution()
        {
            char[] solution = _cipherString.ToCharArray();

            for (int i = 0; i < solution.Length; i++)
            {
                if (char.IsWhiteSpace(solution[i]) || solution[i] == '\n' || solution[i] == '\r')
                    continue;
                solution[i] = '_';
            }

            _solutionString = new string(solution);
            SolutionUpdated.Raise(this, new SolutionEventArgs(_solutionString));
        }
    }
}
